package genetics;

import event.GameOverEvent;
import event.GameOverEventListener;
import java.util.ArrayList;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import tetris.Game;

public class Grid
{

    public ArrayList<Individual> population = new ArrayList<Individual>();
    private int populationSize = 512;
    private int battleSize = 3;
    private int mutationRisk = 1000;
    private int discSize = 11; //endast jämna tal!!!
    private double maxSectorValue = 100;
    private Individual bestIndividual;
    private int generations = 0;
    private Game game;

    public Grid(Game game)
    {
        this.game = game;
    }

    public int getDiscSize()
    {
        return this.discSize;
    }

    public void setSeed(long seed)
    {
        game.setSeed(seed);
    }

    /**
     * Always call this one first before calling Games();
     */
    public void populateGrid()
    {
        for (int i = 0; i < populationSize; i++) {
            double[] newDisc = new double[discSize];
            for (int j = 0; j < discSize; j++) {
                newDisc[j] = randomSector();
            }
            population.add(new Individual(newDisc));
        }

        double[] bestDisc = new double[discSize];

        for (int j = 0; j < discSize; j++) {
            bestDisc[j] = randomSector();
        }

        bestIndividual = new Individual(bestDisc, evaluate(bestDisc));

        for (Individual individual : population) {
            individual.setUtility(evaluate(individual.getIdentityDisc()));

            if (individual.getUtility() > bestIndividual.getUtility()) {
                bestIndividual = individual;
            }
        }

    }

    private double randomSector()
    {
        double i = (Math.random() * maxSectorValue);
        return i;
    }

    public int getPopulationSize()
    {
        return populationSize;
    }

    private Individual getRandomIndividual(ArrayList<Individual> pool)
    {
        if (pool.isEmpty()) {
            return null;
        }

        int index = (int) (Math.random() * pool.size());

        Individual indi = pool.get(index);
        pool.remove(index);

        return indi;
    }

    public Individual[] recognizer()
    {
        Individual[] combatants = new Individual[battleSize];

        for (int i = 0; i < battleSize; i++) {
            combatants[i] = population.get((int) (Math.random() * populationSize));
        }

        return combatants;
    }

    public void games()
    {
        ArrayList<Individual> nextGeneration = new ArrayList<Individual>();
        ArrayList<Individual> winnerPool = new ArrayList<Individual>();
        Individual bestThisGeneration = null;


        // Create winnerPool.
        while (winnerPool.size() < (populationSize * 0.8)) {
            winnerPool.add(discBattle(recognizer()));
        }

        // Crossover the winnerPool
        Individual[] crossover = null;
        Individual[] randoms = null;
        while (true) {
            randoms = new Individual[2];
            if ((randoms[0] = getRandomIndividual(winnerPool)) == null) {
                break;
            }
            if ((randoms[1] = getRandomIndividual(winnerPool)) == null) {
                break;
            }
            crossover = crossover(randoms[0], randoms[1]);
            nextGeneration.add(crossover[0]);
            nextGeneration.add(crossover[1]);
        }


        // Add up to 90% with mutated individuals.
        while (nextGeneration.size() < (populationSize * 0.9)) {
            nextGeneration.add(mutate(population.get((int) (Math.random() * populationSize))));
        }


        // Add top ten individuals to nextGen
        Individual[] topTen = getTopTen(population);
        for (int i = 0; i < topTen.length; i++) {
            nextGeneration.add(topTen[i]);
        }

        // Fill the rest with random individuals
        while (nextGeneration.size() != populationSize) {
            double[] newDisc = new double[discSize];
            for (int j = 0; j < discSize; j++) {
                newDisc[j] = randomSector();
            }
            nextGeneration.add(new Individual(newDisc, evaluate(newDisc)));
        }


        population = (ArrayList<Individual>) nextGeneration.clone();
        bestThisGeneration = population.get(0);

        for (Individual individual : population) {
            individual.setUtility(evaluate(individual.getIdentityDisc()));
            if (individual.getUtility() > bestIndividual.getUtility()) {
                bestIndividual = individual;
            }
            if (individual.getUtility() > bestThisGeneration.getUtility()) {
                bestThisGeneration = individual;
            }
        }


        String discString = "";
        String discString2 = "";
        for (int i = 0; i < discSize; i++) {
            discString += " " + bestIndividual.getIdentityDisc()[i];
            discString2 += " " + bestThisGeneration.getIdentityDisc()[i];
        }

        generations++;
        System.out.println("Generation: " + generations);
        System.out.println("Best Individual:" + discString + ". Utility: " + bestIndividual.getUtility());
        System.out.println("Best Individual This Generation:" + discString2 + ". Utility: " + bestThisGeneration.getUtility());
    }

    private Individual discBattle(Individual[] combatants)
    {
        Individual winner = combatants[0];

        for (Individual combatant : combatants) {
            if (winner.getUtility() < combatant.getUtility()) {
                winner = combatant;
            }
        }

        return winner;
    }

    private Individual[] crossover(Individual mother, Individual father)
    {
        int crossoverIndex = (new Random()).nextInt(discSize - 1);
        if (crossoverIndex == 0) {
            crossoverIndex = 1;
        }
        double[] geneString = new double[discSize];
        double mutation;

        Individual[] rectified = new Individual[2];

        for (int i = 0; i < discSize; i++) {
            if (i < crossoverIndex) {
                mutation =
                        Math.random() * mutationRisk;
                if (mutation <= 1) {
                    geneString[1] =
                            randomSector();
                } else {
                    geneString[i] = mother.getSector(i);
                }
            } else {
                mutation = Math.random() * mutationRisk;
                if (mutation <= 1) {
                    geneString[1] = randomSector();
                } else {
                    geneString[i] =
                            father.getSector(i);
                }
            }
        }
        rectified[0] = new Individual(geneString);
        rectified[0].setUtility(evaluate(rectified[0].getIdentityDisc()));

        for (int i = 0; i < discSize; i++) {
            if (i < crossoverIndex) {
                mutation =
                        Math.random() * mutationRisk;
                if (mutation <= 1) {
                    geneString[1] =
                            Math.random() * maxSectorValue;
                } else {
                    geneString[i] =
                            father.getSector(i);
                }
            } else {
                mutation = Math.random() * mutationRisk;
                if (mutation <= 1) {
                    geneString[1] = Math.random() * maxSectorValue;
                } else {
                    geneString[i] = mother.getSector(i);
                }
            }
        }
        rectified[1] = new Individual(geneString);
        rectified[1].setUtility(evaluate(rectified[1].getIdentityDisc()));
        return rectified;
    }

    private double evaluate(double[] informationDisc)
    {
        /*
         * double x1 = informationDisc[0]; double x2 = informationDisc[1];
         *
         * double f1 = ((Math.pow(x1, 2)) + (4 * x1) + 2); double f2 =
         * (Math.pow(x2, 4) - Math.pow(x2,3) - 4 * Math.pow(x2,2) + ((2*x2) +
         * 5)); * System.out.println("F1:---" + f1); System.out.println("F2:---"
         * + f2); if(f1 < 0) return (-f1 + f2) * -1; else return (f1 + f2) * -1;
         */

        return tetrisFitness(informationDisc, game);
    }
    
    public int fitness = 0;
    public boolean eventFired = false;
    private double bestFitness = 0;

    public double tetrisFitness(double[] disc, Game game)
    {
        fitness = 0;
        game.ai.setDisc(disc);

        game.addMyEventListener(new GameOverEventListener()
        {

            public void myEventOccurred(GameOverEvent evt)
            {
                fitness = Integer.parseInt(evt.getSource().toString());
                eventFired = true;
                //System.out.println(fitness);
            }
        });
        game.tester();

        while (!eventFired) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException ex) {
                Logger.getLogger(Grid.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        eventFired = false;
//
//        if(fitness > bestFitness)
//        {
//            bestFitness = fitness;
//            System.out.println(bestFitness);
//        }
        return fitness;
    }

    /**
     *
     * @param disc
     * @param seed
     * @return Fitness
     */
    public int playSingleGame(double[] disc)
    {
        fitness = 0;
        game.ai.setDisc(disc);

        game.addMyEventListener(new GameOverEventListener()
        {

            public void myEventOccurred(GameOverEvent evt)
            {
                fitness = Integer.parseInt(evt.getSource().toString());
                eventFired = true;
                //System.out.println(fitness);
            }
        });
        game.tester();

        while (!eventFired) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException ex) {
                Logger.getLogger(Grid.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        eventFired = false;

        return fitness;
    }

    private Individual[] getTopTen(ArrayList<Individual> pool)
    {
        Individual[] topTen = new Individual[10];
        for (int i = 0; i < topTen.length; i++) {
            topTen[i] = new Individual(new double[discSize], 0);
        }

        for (Individual individual : pool) {
            for (int i = 0; i < topTen.length; i++) {
                if (topTen[i].getUtility() < individual.getUtility()) {
                    topTen[i] = new Individual(individual.getIdentityDisc(), individual.getUtility());
                    break;
                }
            }
        }

        return topTen;
    }

    private Individual mutate(Individual indi)
    {
        double[] mutatedDisc = new double[discSize];

        for (int i = 0; i < discSize; i++) {
            if ((Math.random() * 0.3) <= 1) {
                mutatedDisc[i] = randomSector();
            } else {
                mutatedDisc[i] = indi.getSector(i);
            }
        }

        return new Individual(mutatedDisc);
    }
}
